/*
 * Copyright 2000-2011 Worth Enterprises, Inc.  All rights reserved.
 */
package com.worthent.foundation.util.state;

import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class defines a state in a state transition table.
 * 
 * Each state definition has the string name of the state and a list of
 * transitions out of the state. Each transition is identified by an event (i.e.
 * there is one transition defined for each event expected at the state). The
 * transition indicates which state to transition to after all the actions are
 * carried out. The transition also specifies a list of actions to carry out on
 * this state transition.
 * <p>
 * The state table performs work through the state table actions. There is an
 * StateActor registered for each action string defined in the table (the
 * initialize method checks for this). When the state table sees an event, it
 * finds the current state, looks for the transition corresponding to the event
 * and runs each actor corresponding to the actions in the state transition
 * action list. When all of the actions are run, the state table runs the
 * registered transitioner (if one was installed). If an error is detected (the
 * StateException or a runtime exception is thrown), the state transition is
 * aborted and the error handler is run (if installed) with a message indicating
 * the state of the table, events, action, and the message portion of the thrown
 * exception.
 * <p>
 * If no transition is specified for a given event, the last transition for the
 * given state is run. The last transition should, therefore, be established as
 * the <strong>default</strong> transition to catch unexpected events.
 * 
 * @author Erik K. Worth
 * @version $Id: StateDef.java 2 2011-11-28 00:10:06Z erik.k.worth@gmail.com $
 */
public class StateDef {

    /** Logger for this class */
    private static final Logger LOGGER =
        LoggerFactory.getLogger(StateDef.class);

    /** State name indicating no state */
    public static final String NO_STATE = "#NotInitialized#";

    /**
     * State name for the current state. Use this to transition to the the
     * current state when processing an event. The state history is updated to
     * reflect that the prior state is current state.
     */
    public static final String STAY_IN_STATE = "#ThisState#";

    /**
     * State name for the previous state. Use this to transition to whatever the
     * previous state was after successfully processing an event.
     */
    public static final String GOTO_PREVIOUS_STATE = "#PreviousState#";

    /**
     * State name used to tell the engine to refrain from updating the state in
     * the history. This state is designated in state transitions where the
     * actor explicitly modifies the state table state during event processing.
     */
    public static final String STATE_CHANGE_BY_ACTOR = "#StateChangeByActor#";

    /** The identifier for this state */
    private final String name;

    /**
     * The list of {@link StateTransitionDef} objects providing instructions
     * from transitioning from this state to other states
     */
    private final Map<String, StateTransitionDef> transitions;

    /** The default transition is the last one defined in the list */
    private final StateTransitionDef defaultTransition;

    /**
     * Defines a state in the state transition table along with a number of
     * state transitions.
     * 
     * @param name the name of the state
     * @param transitions the transition definitions from this state to other
     *        states
     * @throws StateDefException thrown when there is an error in the state
     *         definition
     */
    public StateDef(final String name, final StateTransitionDef... transitions)
        throws StateDefException {
        this(
            name,
            Arrays.asList(transitions),
            StateTransitionDef.UNEXPECTED_EVENT);
    }

    /**
     * Defines a state in the state transition table along with a number of
     * state transitions.
     * 
     * @param name the name of the state
     * @param transitions the transition definitions from this state to other
     *        states
     * @throws StateDefException thrown when there is an error in the state
     *         definition
     */
    public StateDef(
        final String name,
        final List<StateTransitionDef> transitions) throws StateDefException {
        this(name, transitions, StateTransitionDef.UNEXPECTED_EVENT);
    }

    /**
     * Defines a state in the state transition table. Instances of this object
     * define a state and the transitions out of the state.
     * 
     * @param name the name of the state
     * @param transitions the list of transitions from this state to other
     *        states or to the same state
     * @param defaultTransition transition taken when the received event does
     *        not match one specified on any of the transitions
     */
    public StateDef(
        final String name,
        final List<StateTransitionDef> transitions,
        final StateTransitionDef defaultTransition) {
        if (null == name) {
            throw new IllegalArgumentException("name must not be null");
        }
        if (null == transitions) {
            throw new IllegalArgumentException("transitions must not be null");
        }
        if (transitions.isEmpty()) {
            throw new IllegalArgumentException(
                "The transitions list must not be empty");
        }
        this.name = name;
        this.transitions = new LinkedHashMap<String, StateTransitionDef>();
        for (final StateTransitionDef transition : transitions) {
            final String eventName = transition.getEventName();
            this.transitions.put(eventName, transition);
        }
        this.defaultTransition = defaultTransition;
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Defined State Table State Definition {}", name);
        }
    }

    /**
     * Returns the identifier for this state.
     */
    public final String getName() {
        return name;
    }

    /**
     * Returns the <code>StateTransitionDef</code> that originate from this
     * state.
     */
    public final Collection<StateTransitionDef> getTransitions() {
        return transitions.values();
    }

    /**
     * Returns the <code>StateTransitionDef</code> that handles the transition
     * for the specified event name or <code>null</code> if no transition is
     * configured for the specified event name.
     */
    public final StateTransitionDef getTransitionForEvent(final String eventName) {
        return transitions.get(eventName);
    }

    /**
     * Returns the last state transition defined as the default or
     * <code>null</code> if no transitions have been defined.
     */
    public StateTransitionDef getDefaultTransition() {
        return defaultTransition;
    }

}
